package de.codi.wrapper;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLOntology;

import de.codi.CODISettingID;
import de.codi.CODISettings;
import de.codi.SettingsHandler;
import de.codi.exception.CodiException;
import de.codi.read.Storage;
import de.codi.similarity.filler.StructuralFiller;
import de.codi.similarity.sparematrix.Cell;
import de.codi.similarity.sparematrix.Generator;
import de.codi.similarity.sparematrix.merger.MaxMerger;
import de.codi.similarity.sparematrix.merger.SpareMatrixMerger;
import de.codi.similarity.sparematrix.merger.WeightMerger;
import de.codi.string.StringSimilarity;
import de.codi.string.metrics.CosineSim;
import de.codi.string.metrics.FastJoinSimilarity;
import de.codi.string.metrics.FastJoinSimilarity.FastJoinSim;
import de.codi.string.metrics.JaccartSim;
import de.codi.string.metrics.JakobTimoSim;
import de.codi.string.metrics.JaroWinklerSim;
import de.codi.string.metrics.LevenshteinSim;
import de.codi.string.metrics.OverlapSim;
import de.codi.string.metrics.SmithWatermanSim;
import de.codi.structural.RockItStandardMatcher;
import de.codi.xtest.SealsTDRS_finalTest;
import de.unima.alcomox.ExtractionProblem;
import de.unima.alcomox.Settings;
import de.unima.alcomox.exceptions.AlcomoException;
import de.unima.alcomox.exceptions.MappingException;
import de.unima.alcomox.mapping.Characteristic;
import de.unima.alcomox.mapping.Correspondence;
import de.unima.alcomox.mapping.Mapping;
import de.unima.alcomox.ontology.LocalOntology;


public class Main
{

    private ExtractionProblem ep;

    private static File       output    = null;
    private static Mapping    reference = null;


    public static void main(String[] args) throws MalformedURLException, IOException
    {
        Main main = new Main();
        URI uri = null;
        URI uri2 = null;

        try {
            uri = new URI(args[0]);
            uri2 = new URI(args[1]);
            output = new File(args[2]);
            output.createNewFile();

            if(args.length > 3 && args[3] != null && args[3].contains("codi.properties")) {
                CODISettings.PATH_CS = args[3];
            } else {
                URI referenceUri = null;
                if(args.length > 3) {
                    referenceUri = new URI(args[3]);
                    reference = new Mapping(referenceUri.toString());
                }
                if(args.length > 4 && args[4] != null) {
                    CODISettings.PATH_CS = args[4];
                }
            }
        } catch(IOException e) {
            System.err.println("error: invalid output path!");
            System.err.println("output patch: " + output.getAbsolutePath());
            System.exit(1);
        } catch(URISyntaxException e) {
            e.printStackTrace();
            System.err.println("error: invalid URI!");
            System.err.println("e.g. java -jar codi.jar URI_Ontology1 URI_Ontology2 output_alignment.rdf [referenceAlignment] [codi.properties]");
            System.exit(1);
        } catch(NullPointerException e) {
            e.printStackTrace();
            System.err.println("error: missing parameter!");
            System.err.println("e.g. java -jar codi.jar URI_Ontology1 URI_Ontology2 output_alignment.rdf [referenceAlignment] [codi.properties]");
            System.exit(1);
        } catch(ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.err.println("error: missing parameter!");
            System.err.println("e.g. java -jar codi.jar URI_Ontology1 URI_Ontology2 output_alignment.rdf [referenceAlignment] [codi.properties]");
            System.exit(1);
        } catch(MappingException e) {
            e.printStackTrace();
        }

        try {
            Long start = System.currentTimeMillis();
            URI result = main.align(uri, uri2);
            System.out.println("aligned in " + (System.currentTimeMillis() - start) + " milliseconds.");
            System.out.println("Result: " + result.toString());
            System.out.println("Finished!");
        } catch(AlcomoException e) {
            e.printStackTrace();
        }
    }


    public URI align(URI uri, URI uri2) throws AlcomoException, MalformedURLException, IOException
    {
        if(CODISettings.PATHDETECTION) {
            File file = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().getPath());
            String path = file.getParent();
            System.out.println("DEBUG: " + path);
            if(!new File(path + System.getProperty("file.separator") + "codi.properties").exists()) {
                path = file.getParent();
            }
            path = path.replaceAll("%20", " ");
            CODISettings.PATH = path + System.getProperty("file.separator");
            CODISettings.PATH = CODISettings.PATH.replace("%20", " ");
        }

        // store the ontologies to be matched
        try {
            Storage.storeSource(uri);
            Storage.storeTarget(uri2);
        } catch(CodiException e) {
            System.err.println("Failed to set source/target Path!");
            e.printStackTrace();
        }

        int entitySum = Storage.source.cl.size() + Storage.source.op.size() + Storage.source.dp.size() + Storage.target.cl.size() + Storage.target.op.size() + Storage.target.dp.size();

        CODISettingID track = CODISettingID.NONE;
        try {
            track = SettingsHandler.loadSettings();
        } catch(Exception e) {
            System.out.println("ERROR! - CANNOT LOAD SETTINGS FILE 'codi.properties'");
        }

        // TODO: remove next line
        // track = CODISettingID.FASTJOIN;

        if(track == CODISettingID.AUTO) {
            System.out.println("DEBUG: start setting detection");
            if(isOBOOnt()) {
                System.out.println("DEBUG: 0 - Biomedical Setting");
                track = CODISettingID.BIOMEDICAL;
            } else {
                if(entitySum >= CODISettings.detectionLimit) {
                    track = CODISettingID.STANDARD;
                } else {
                    Mapping structuralMapping = new Mapping();
                    try {
                        Set<Correspondence> set = new HashSet<Correspondence>();
                        set.addAll(getCorrespondence(Storage.source.cl, Storage.target.cl));
                        set.addAll(getCorrespondence(Storage.source.op, Storage.target.op));
                        set.addAll(getCorrespondence(Storage.source.dp, Storage.target.dp));

                        structuralMapping.setCorrespondences(set);
                    } catch(CodiException e) {
                        System.out.println("Failed during 'toRockitString'");
                        e.printStackTrace();
                    }

                    double tt = determineTaskType(structuralMapping, uri, uri2);
                    System.out.println("DEBUG: tt - " + tt);
                    if(tt > CODISettings.versionSettingSplitValue) {
                        System.out.println("DEBUG: 1 - Version-Matching Setting");
                        track = CODISettingID.VERSION;
                    } else {
                        System.out.println("DEBUG: 2 - Non-Version-Matching Setting");
                        track = CODISettingID.STANDARD;
                    }
                }
            }
        }

        switch(track) {
            case BIOMEDICAL:
                if(!CODISettings.overrideSettings) {
                    CODISettings.SIMPLE_THRESHOLD_DEFAULT = 0;
                }
                CODISettings.structuralFiller = false;
                CODISettings.individualFiller = false;
                CODISettings.penaltyString = true;
                CODISettings.preliminaryString = false;
                CODISettings.mergerString = 1;

                CODISettings.stringNotEqualsPenalty = false;
                CODISettings.stringFillerCosine = false;
                CODISettings.stringFillerJaccard = false;
                CODISettings.stringFillerJaroWinkler = false;
                CODISettings.stringFillerLevensthein = false;
                CODISettings.stringFillerOverlapoverlapCoefficient = false;
                CODISettings.stringFillerSmithWatermanGotoh = false;
                CODISettings.stringFillerJakobTimo = true;

                CODISettings.alcomoSimple = true;
            break;
            case VERSION:
                if(!CODISettings.overrideSettings) {
                    CODISettings.SIMPLE_THRESHOLD_DEFAULT = 0.6;
                }
                CODISettings.structuralFiller = true;
                CODISettings.individualFiller = true;
                CODISettings.penaltyString = true;
                CODISettings.preliminaryString = false;
                CODISettings.mergerString = 0;

                CODISettings.stringNotEqualsPenalty = true;
                CODISettings.stringFillerCosine = true;
                CODISettings.stringFillerJaccard = true;
                CODISettings.stringFillerJaroWinkler = true;
                CODISettings.stringFillerLevensthein = true;
                CODISettings.stringFillerOverlapoverlapCoefficient = true;
                CODISettings.stringFillerSmithWatermanGotoh = true;
                CODISettings.stringFillerJakobTimo = true;

                CODISettings.alcomoSimple = true;

            /*
             * CODISettings.subsumptionStabilityWeight = 0.1;
             * CODISettings.domainStabilityWeight = 0.1;
             * CODISettings.rangeStabilityWeight = 0.1;
             */

            break;
            case STANDARD:
                if(!CODISettings.overrideSettings) {
                    CODISettings.SIMPLE_THRESHOLD_DEFAULT = 0.867;
                }
                CODISettings.structuralFiller = false;
                CODISettings.individualFiller = false;
                CODISettings.penaltyString = false;
                CODISettings.preliminaryString = true;
                CODISettings.mergerString = 0;

                CODISettings.stringNotEqualsPenalty = false;
                CODISettings.stringFillerCosine = true;
                CODISettings.stringFillerJaccard = true;
                CODISettings.stringFillerJaroWinkler = true;
                CODISettings.stringFillerLevensthein = true;
                CODISettings.stringFillerOverlapoverlapCoefficient = true;
                if(entitySum < CODISettings.detectionLimit) {
                    CODISettings.stringFillerSmithWatermanGotoh = true;
                }
                CODISettings.stringFillerJakobTimo = false;

                CODISettings.alcomoSimple = false;
            break;
            case FASTJOIN:
                // if(!CODISettings.overrideSettings) {
                CODISettings.SIMPLE_THRESHOLD_DEFAULT = 0.0;
                // }
                CODISettings.structuralFiller = false;
                CODISettings.individualFiller = false;
                CODISettings.penaltyString = false;
                CODISettings.preliminaryString = false;
                CODISettings.mergerString = 1;

                CODISettings.stringNotEqualsPenalty = false;
                CODISettings.stringFillerCosine = false;
                CODISettings.stringFillerJaccard = false;
                CODISettings.stringFillerJaroWinkler = false;
                CODISettings.stringFillerLevensthein = false;
                CODISettings.stringFillerOverlapoverlapCoefficient = false;
                CODISettings.stringFillerSmithWatermanGotoh = false;
                CODISettings.stringFillerJakobTimo = false;
                CODISettings.stringFillerFastJoin = true;

                CODISettings.alcomoSimple = true;
            case NONE:
            // Comment: use property file -> nothing to do here
            break;
            default:
            // nothing
            break;
        }

        // if(true)
        // return null;
        // string metrics
        List<StringSimilarity> sim = new ArrayList<StringSimilarity>();
        if(CODISettings.stringFillerCosine)
            sim.add(new CosineSim());
        if(CODISettings.stringFillerLevensthein)
            sim.add(new LevenshteinSim());
        if(CODISettings.stringFillerJaroWinkler)
            sim.add(new JaroWinklerSim());
        if(CODISettings.stringFillerSmithWatermanGotoh && entitySum < CODISettings.detectionLimit)
            sim.add(new SmithWatermanSim());
        if(CODISettings.stringFillerJaccard)
            sim.add(new JaccartSim());
        if(CODISettings.stringFillerOverlapoverlapCoefficient)
            sim.add(new OverlapSim());
        if(CODISettings.stringFillerJakobTimo)
            sim.add(new JakobTimoSim());
        if(CODISettings.stringFillerFastJoin)
            sim.add(new FastJoinSimilarity(Storage.source.cl, Storage.target.cl, FastJoinSim.FJACCARD, 0.8, 0.5));

        // Merger: weight or max
        SpareMatrixMerger merger;
        switch(CODISettings.mergerString) {
            case 0:
                merger = new WeightMerger();
            break;
            case 1:
                merger = new MaxMerger();
            break;
            default:
                merger = new WeightMerger();
        }

        Generator classGen = new Generator(Storage.source.cl, Storage.target.cl, sim, merger);
        Generator opropGen = new Generator(Storage.source.op, Storage.target.op, sim, merger);
        Generator dpropGen = new Generator(Storage.source.dp, Storage.target.dp, sim, merger);

        /*
         * write mapping after string measures:
         * try {
         * Mapping part1 = new Mapping();
         * part1 = part1.getUnion(classGen.toMapping());
         * part1 = part1.getUnion(opropGen.toMapping());
         * part1 = part1.getUnion(dpropGen.toMapping());
         * part1.write("out.rdf", Mapping.FORMAT_RDF);
         * System.exit(0);
         * } catch (Exception e){
         * e.printStackTrace();
         * }
         */

        Mapping mapping = null;
        try {
            ArrayList<String[]> cList = classGen.toRockitString();
            ArrayList<String[]> oList = opropGen.toRockitString();
            ArrayList<String[]> dList = dpropGen.toRockitString();

            // rockIt
            // String debugLog = output.getAbsolutePath().substring(0, output.getAbsolutePath().lastIndexOf(File.separator) + 1);
            RockItStandardMatcher matcher = new RockItStandardMatcher();

            mapping = matcher.match(cList, oList, dList);
        } catch(CodiException e) {
            System.out.println("Failed during 'toRockitString'");
            e.printStackTrace();
        }

        if(output == null) {
            try {
                output = File.createTempFile("align_", ".rdf");
            } catch(IOException e) {
                System.err.println("ERROR! Could not create Files (tmp)!");
                e.printStackTrace();
            }
        }

        Mapping matcherAlignment = getAlcomoMapping(mapping, uri, uri2);
        matcherAlignment.write(output.getAbsolutePath());

        if(reference != null) {
            Characteristic c = new Characteristic(matcherAlignment, reference);
            System.out.println(c.toString());
        }

        return output.toURI();
    }


    @SuppressWarnings ("deprecation")
    private Mapping getAlcomoMapping(Mapping mapping, URI uri, URI uri2) throws AlcomoException
    {
        // alocomo
        LocalOntology sourceOnt = new LocalOntology(uri.toString());
        LocalOntology targetOnt = new LocalOntology(uri2.toString());
        Settings.ONE_TO_ONE = true;

        ep = null;

        if(CODISettings.alcomoSimple) {
            ep = new ExtractionProblem(ExtractionProblem.ENTITIES_CONCEPTSPROPERTIES, ExtractionProblem.METHOD_GREEDY, ExtractionProblem.REASONING_EFFICIENT);
        } else {
            ep = new ExtractionProblem(ExtractionProblem.ENTITIES_CONCEPTSPROPERTIES, ExtractionProblem.METHOD_OPTIMAL, ExtractionProblem.REASONING_COMPLETE);
        }

        // bind the ontologies and the mapping to the extraction problem
        ep.bindSourceOntology(sourceOnt);
        ep.bindTargetOntology(targetOnt);
        ep.bindMapping(mapping);

        Mapping extractedMapping = mapping;

        // solve the problem
        Thread alcomoSolve = new Thread() {
            @Override
            public void run()
            {
                try {
                    ep.solve();
                } catch(Exception e) {
                    System.err.println("alcomo failed - thread goes sleeping...");
                    // e.printStackTrace();
                    try {
                        Thread.currentThread();
                        Thread.sleep(CODISettings.alcomoTimeOut + 60000);
                    } catch(InterruptedException ie) {
                        ie.printStackTrace();
                    }
                }
            }
        };
        alcomoSolve.start();

        // wait for alcomo
        synchronized(alcomoSolve) {
            try {
                alcomoSolve.wait(CODISettings.alcomoTimeOut + 1);
                if(!alcomoSolve.isAlive()) {
                    extractedMapping = ep.getExtractedMapping();
                } else {
                    // There is no other possibility. To solve this problem we have to change the solve method.
                    alcomoSolve.stop();
                    System.out.println("Alcomo solve stopped!");
                }
            } catch(InterruptedException e) {
                System.err.println("ERROR! Could not wait for alcomo!");
                e.printStackTrace();
            }
        }

        return extractedMapping;
    }


    private boolean isOBOOnt()
    {
        OWLOntology o1 = Storage.source.ont;
        OWLOntology o2 = Storage.target.ont;

        for(OWLAnnotation oa : o1.getAnnotations()) {
            if(oa.getProperty().toString().contains("oboInOwl")) { return true; }
        }
        for(OWLAnnotation oa : o2.getAnnotations()) {
            if(oa.getProperty().toString().contains("oboInOwl")) { return true; }
        }
        return false;
    }


    @SuppressWarnings ("deprecation")
    private double determineTaskType(Mapping simMapping, URI sourcePath, URI targetPath)
    {
        Mapping structuralMapping = simMapping.getCopy();
        structuralMapping.normalize();

        int numOfSourceEntities = Storage.source.classes.size() + Storage.source.dataProperties.size() + Storage.source.objectProperties.size();
        int numOfTargetEntities = Storage.target.classes.size() + Storage.target.dataProperties.size() + Storage.target.objectProperties.size();
        int minNumOfEntities = Math.min(numOfSourceEntities, numOfTargetEntities);

        Settings.ONE_TO_ONE = true;
        Settings.DISABLE_REASONING = true;
        Mapping extractedMapping = new Mapping();
        try {
            LocalOntology sourceOnt = new LocalOntology(sourcePath.toString());
            LocalOntology targetOnt = new LocalOntology(targetPath.toString());
            final ExtractionProblem ep = new ExtractionProblem(ExtractionProblem.ENTITIES_CONCEPTSPROPERTIES, ExtractionProblem.METHOD_OPTIMAL_HUN, ExtractionProblem.REASONING_EFFICIENT);
            ep.bindSourceOntology(sourceOnt);
            ep.bindTargetOntology(targetOnt);
            ep.bindMapping(structuralMapping);

            // solve the problem
            Thread alcomoSolve = new Thread() {
                @Override
                public void run()
                {
                    try {
                        ep.solve();
                    } catch(Exception e) {
                        System.err.println("alcomo failed - thread goes sleeping...");
                        // e.printStackTrace();
                        try {
                            Thread.currentThread();
                            Thread.sleep(CODISettings.alcomoTimeOut + 60000);
                        } catch(InterruptedException ie) {
                            ie.printStackTrace();
                        }
                    }
                }
            };
            alcomoSolve.start();

            // wait for alcomo
            synchronized(alcomoSolve) {
                try {
                    alcomoSolve.wait(CODISettings.alcomoTimeOut + 1);
                    if(!alcomoSolve.isAlive()) {
                        extractedMapping = ep.getExtractedMapping();
                    } else {
                        // There is no other possibility. To solve this problem we have to change the solve method.
                        alcomoSolve.stop();
                        System.out.println("Alcomo solve stopped!");
                    }
                } catch(InterruptedException e) {
                    System.err.println("ERROR! Could not wait for alcomo!");
                    e.printStackTrace();
                }
            }
        } catch(AlcomoException e) {
            e.printStackTrace();
        }
        Settings.DISABLE_REASONING = false;
        double score = extractedMapping.getConfidenceTotal() / (double) minNumOfEntities;
        System.out.println(extractedMapping.getConfidenceTotal() + " [" + extractedMapping.size() + "] " + " (" + structuralMapping.size() + " -> " + structuralMapping.getConfidenceTotal() + ") " + minNumOfEntities);

        SealsTDRS_finalTest.tmp = score + "";

        return score;
    }


    private Set<Correspondence> getCorrespondence(Set<OWLEntity> sE, Set<OWLEntity> tE) throws CodiException
    {
        List<Cell<? extends OWLEntity>> list = new ArrayList<Cell<? extends OWLEntity>>();

        for(OWLEntity e1 : sE) {
            for(OWLEntity e2 : tE) {
                double confidenceValue = StructuralFiller.fill(e1, e2);
                list.add(new Cell<OWLEntity>(e1, e2, confidenceValue));
            }
        }

        ArrayList<String[]> result = new ArrayList<String[]>();
        Set<Correspondence> res2 = new HashSet<Correspondence>();
        for(int i = 0; i < list.size(); i++) {
            if(list.get(i).getConfidenceValue() != 0) {
                // result.add(list.get(i).toRockitString());
                Cell<? extends OWLEntity> cell = list.get(i);
                String[] rs = new String[] { cell.getSourceEntity().toString(), "<" + cell.getTargetEntity().getIRI().toString() + ">", Double.toString(cell.getConfidenceValue()) };
                if(cell.getTargetEntity().getIRI().getFragment() == null) {
                    rs[1] = "<" + Storage.target.ont.getOntologyID().getOntologyIRI().toString() + "#" + cell.getTargetEntity().getIRI().toURI().toString() + ">";
                }
                res2.add(cell.toCorrespondence());
                result.add(rs);
            }
        }
        return res2;
    }
}